package controller.editorcontroller;

import java.awt.Container;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Shape;
import java.awt.dnd.DragSource;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.swing.JDialog;
import javax.swing.JTextField;
import javax.swing.JToolTip;
import javax.swing.JViewport;
import javax.swing.event.MouseInputListener;

import locsi.UserSettings;
import model.LogicType;
import model.editormodel.EditorModel;
import model.editormodel.Input;
import model.editormodel.LogicEditorElement;
import model.editormodel.LogicSelfdefinedEditorElement;
import model.editormodel.Output;
import view.UtilityConstants;
import view.UtilityLogicDesigns;
import view.commonview.ButtonValues;
import view.commonview.LogicViewElement;
import view.commonview.SelfdefinedPicturePanel;
import view.commonview.TabbedPane;
import view.commonview.ToolBar;
import view.editorview.ComponentPanel;
import view.editorview.EditorArea;

/**
 * <b>Implementiert:</b> <code>MouseInputListener</code> <br>
 * <b>In Verbindung stehende Klassen:</b>
 * <code>EditorArea, ToolBar, EditorModel, TabbedPane, ComponentPanel, LogicEditorElement</code>
 * <br>
 * <br>
 * Listener, der auf der <code>EditorArea</code> liegt. Je nach Toolmodus werden
 * entsprechende Methoden zum Hinzufuegen, Loeschen, Verschieben, Umbenennen,
 * Markieren und Verbinden von Bauteilen des <code>EditorModel</code> bzw. der
 * <code>EditorArea</code> aufgerufen.
 * 
 * @author Team Locsi
 * 
 */
public class EditorAreaListener implements MouseInputListener {

	/*
	 * Referenzen auf View-Objekte
	 */
	private EditorArea eArea;
	private ToolBar toolBar;
	private EditorModel eModel;
	private TabbedPane tabs;
	private ComponentPanel compPanel;
	private LogicEditorElement nodeElement;
	private Object firstConnectionPoint;

	// startpunkt wenn man eine leitung zieht
	private double startXfc;
	private double startYfc;

	// startpunkt fuer das auswahlrechteck
	private double startX;
	private double startY;

	// zum verschieben von bauteilen
	private double moveX;
	private double moveY;
	private boolean objectDragged;
	private boolean markTangleExists;
	// Punkt: eigentlicher Klickpunkt mit Zoomfaktor verrechnet
	private Point2D mewz = new Point2D.Double();

	// Cursor
	private Cursor custom = new Cursor(Cursor.DEFAULT_CURSOR);
	private Cursor hand = new Cursor(Cursor.HAND_CURSOR);
	private Cursor move = new Cursor(Cursor.MOVE_CURSOR);
	private Cursor crosshair = new Cursor(Cursor.CROSSHAIR_CURSOR);
	private Cursor error = DragSource.DefaultMoveNoDrop;

	private JToolTip tp = new JToolTip();
	private JTextField nameField;
	private String oldNameForRename;
	private JDialog showPicPanel;

	/**
	 * Dieser Konstruktor erzeugt ein neues <code>EditorAreaListener</code> -
	 * Objekt.
	 * 
	 * @param eArea
	 *            <code>EditorArea</code> - Referenz auf die
	 *            <code>EditorArea</code>
	 * @param toolBar
	 *            <code>ToolBar</code> - Referenz auf die <code>ToolBar</code>
	 * @param eModel
	 *            <code>EditorModel</code> - Referenz auf das
	 *            <code>EditorModel</code>
	 * @param tabs
	 *            <code>TabbedPane</code> - Referenz auf das
	 *            <code>TabbedPane</code>
	 * @param compPanel
	 *            <code>ComponentPanel</code> - Referenz auf das
	 *            <code>ComponentPanel</code>
	 */
	public EditorAreaListener(EditorArea eArea, ToolBar toolBar,
			EditorModel eModel, TabbedPane tabs, ComponentPanel compPanel) {
		this.tabs = tabs;
		this.eArea = eArea;
		this.eModel = eModel;
		this.toolBar = toolBar;
		this.compPanel = compPanel;
		this.firstConnectionPoint = null;
		this.tp.setSize(80, 20);
		this.tp.setVisible(false);
		this.eArea.add(tp);
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mouseClicked(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());
		switch (this.toolBar.getCurrentMode()) {

		case INSERTION_MODE:

			// Abfrage ob Doppelklick/Trippleklick oder sonstwas gemacht wurde
			// falls ja, wird nix gemacht beim zweiten/dritten klick
			if (arg0.getClickCount() != 1) {
				return;
			}

			// falls kein element angeklickt wurde
			if (this.eArea.containsElement(mewz) == null
					&& compPanel.getActualSelected() != null) {
				if (!addWillIntersect(compPanel.getActualSelected(),
						mewz.getX(), mewz.getY())) {
					if (this.eArea.getComponentPanel().getActualSelected() == LogicType.SELFDEFINED) {
						if (this.eModel.addLogicElement(this.eArea
								.getComponentPanel().getActualSelected(),
								compPanel.getNameOfSelectedSelfDefined(), mewz
										.getX(), mewz.getY()) == null) {
							eArea.getiArea()
									.addInfoText(
											"Hinzufügen fehlgeschlagen, weil das Bauteil fehlerhaft ist");
						}
					} else {
						this.eModel.addLogicElement(this.eArea
								.getComponentPanel().getActualSelected(), null,
								mewz.getX(), mewz.getY());
					}
				}

			} else if (this.eArea.containsElement(mewz) == null) {
				firstConnectionPoint = null;
				nodeElement = null;
				this.eArea.removeNewConnection();
			} else {

				// wurde ein element angeklickt und im componentpanel die option
				// leitung ausgewaehlt
				// null steht hierbei fuer leitung ausgewaehlt
				if (compPanel.getActualSelected() == null) {
					// wurde bis jetzt noch keine leitung gezeichnet
					if (firstConnectionPoint == null) {

						Object object = checkIfInputOrOutput(mewz);

						// testen ob input oder output angeklickt wurde
						if (object != null) {
							firstConnectionPoint = object;
							Point2D po = new Point2D.Double();
							if (firstConnectionPoint instanceof Input) {
								Input inp = (Input) firstConnectionPoint;
								po.setLocation(
										(inp.getXRelToElement()
												+ inp.getMySuperElement()
														.getXCoord() + UtilityConstants.IO_RADIUS / 2),
										(inp.getYRelToElement()
												+ inp.getMySuperElement()
														.getYCoord() + UtilityConstants.IO_RADIUS / 2));
							} else {
								Output out = (Output) firstConnectionPoint;
								po.setLocation(
										(out.getXRelToElement()
												+ out.getMySuperElement()
														.getXCoord() + UtilityConstants.IO_RADIUS / 2),
										(out.getYRelToElement()
												+ out.getMySuperElement()
														.getYCoord() + UtilityConstants.IO_RADIUS / 2));
							}
							this.eArea.initializeNewConnection(po);
						}

						// wurde bereits eine leitung gezeichnet
					} else {

						// es wird geschaut, ob ein weiterer input bzw. output
						// angeklickt wurde
						Object secondConnectionPoint = checkIfInputOrOutput(mewz);

						// wurde zweimal input bzw output geklickt wird die
						// linie geloescht
						if (firstConnectionPoint instanceof Input
								&& secondConnectionPoint instanceof Input) {
						} else if (firstConnectionPoint instanceof Output
								&& secondConnectionPoint instanceof Output) {
						} else {

							// wurde einmal input und einmal output angeklickt,
							// wird die verbindung erstellt
							if (firstConnectionPoint instanceof Input) {
								if (((Input) firstConnectionPoint)
										.getMySuperElement() instanceof LogicSelfdefinedEditorElement) {
									Map<String, List<Input>> inputList = ((LogicSelfdefinedEditorElement) (((Input) firstConnectionPoint)
											.getMySuperElement()))
											.getListOfInputs();
									Input inp = (Input) firstConnectionPoint;
									for (List<Input> list : inputList.values()) {
										if (list.contains(inp)) {
											for (Input i : list) {
												this.eModel
														.setConnection(
																(Output) secondConnectionPoint,
																i);
											}
										}
									}
								} else {
									this.eModel.setConnection(
											(Output) secondConnectionPoint,
											(Input) firstConnectionPoint);
								}
							} else {
								if (secondConnectionPoint == null) {
									this.eArea
											.getiArea()
											.addInfoText(
													"Verbindung setzen nicht möglich, da Eingang bereits belegt");
								} else {
									if (((Input) secondConnectionPoint)
											.getMySuperElement() instanceof LogicSelfdefinedEditorElement) {
										Map<String, List<Input>> inputList = ((LogicSelfdefinedEditorElement) (((Input) secondConnectionPoint)
												.getMySuperElement()))
												.getListOfInputs();
										Input inp = (Input) secondConnectionPoint;
										for (List<Input> list : inputList
												.values()) {
											if (list.contains(inp)) {
												for (Input i : list) {
													this.eModel
															.setConnection(
																	(Output) firstConnectionPoint,
																	i);
												}
											}
										}
									} else {
										this.eModel.setConnection(
												(Output) firstConnectionPoint,
												(Input) secondConnectionPoint);
									}
								}
							}
						}

						// die verbindung wird geloescht, wird der view uebers
						// model uebergeben
						firstConnectionPoint = null;
						nodeElement = null;
						this.eArea.removeNewConnection();
					}
				}
			}
			break;

		case SELECTION_MODE:
			if (arg0.getButton() == MouseEvent.BUTTON1) {
				this.eArea.selectElement(mewz);

			} else if (arg0.getButton() == MouseEvent.BUTTON3) {
				LogicViewElement elem = eArea.containsElement(mewz);
				if (elem != null
						&& LogicType.SWITCH_LAMP_CLOCK.contains(eModel
								.getCircuitModel()
								.getUnmodifableElementsOnEditor()
								.get(elem.getElementsName()).getType())) {
					tabs.resetZoomFactor();
					oldNameForRename = elem.getElementsName();
					nameField = new JTextField(elem.getElementsName());
					nameField.setSize(150, 20);
					nameField.setLocation((int) mewz.getX(), (int) mewz.getY());
					nameField.addKeyListener(new SwitchLampRenameEnterListener(
							nameField, eModel, eArea, oldNameForRename));
					eArea.add(nameField);
				}
			}

			break;

		default:
			break;

		}
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mouseEntered(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mouseExited(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());
		// falls aktuell eine linie gezeichnet wurde, wird diese geloescht
		firstConnectionPoint = null;
		this.eArea.removeNewConnection();
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mousePressed(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());
		if (nameField != null) {
			eArea.remove(nameField);
			nameField = null;
		}

		switch (this.toolBar.getCurrentMode()) {

		case MOVE_MODE:

			// befindet man sich im MOVE-Modus werden die koordinaten des klicks
			// gespeichert (wichtig fuer das verschieben des jviewports)
			moveX = mewz.getX() * tabs.getZoomFactor();
			moveY = mewz.getY() * tabs.getZoomFactor();
			break;

		case SELECTION_MODE:

			// falls bauteil markiert ist, werden die aktuellen koordinaten
			// gespeichert
			// (wichtig fuer die mousedragged-methode)
			if (this.eArea.isElementSelected(mewz)) {
				this.startX = (int) mewz.getX();
				this.startY = (int) mewz.getY();
				this.startXfc = this.startX;
				this.startYfc = this.startY;
			}
			break;
		default:
			break;
		}
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mouseReleased(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());

		// wird die maus losgelassen, werden die internen flags aus der
		// mousedragged-methode auf false gesetzt und das markierungsrechteck
		// in der view geloescht
		if (objectDragged) {

			// falls die zahl der bauteile nicht groesser ist als festgelegt
			if (eArea.getLogicViewElements().size() <= UserSettings.maxElemNoIntersect) {
				List<LogicViewElement> markedList = new ArrayList<LogicViewElement>(
						eArea.getMarkedLogicViewElements().values());
				List<LogicViewElement> notMarkedElements = new ArrayList<LogicViewElement>(
						eArea.getLogicViewElements());
				notMarkedElements.removeAll(markedList);

				// es wird ein großer GeneralPath (Shape) fuer alle markierten
				// Bauteile erzeugt
				GeneralPath compMarkedPath = new GeneralPath();
				for (int i = 0; i < markedList.size(); i++) {
					compMarkedPath.append(markedList.get(i).getDesign(), false);
				}

				// es wird ein großer GeneralPath (Shape) fuer alle nicht
				// markierten
				// Bauteile erzeugt
				GeneralPath compNotMarkedPath = new GeneralPath();
				for (int i = 0; i < notMarkedElements.size(); i++) {
					compNotMarkedPath.append(notMarkedElements.get(i)
							.getDesign(), false);
				}

				boolean moveBack = false;

				// es wird geprueft ob sich die erzeugten großen
				// zusammenhaengenden
				// shapes schneiden
				if (eArea.shapesIntersect(compMarkedPath, compNotMarkedPath)) {
					moveBack = true;
				}

				compMarkedPath = null;
				compNotMarkedPath = null;

				if (moveBack) {
					eModel.moveElements(eArea.getNamesOfSelectedElements(),
							startXfc - mewz.getX(), startYfc - mewz.getY());
				}
			}
		}
		objectDragged = false;
		this.eArea.removeMarkTangle();
		this.markTangleExists = false;
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mouseDragged(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());
		switch (this.toolBar.getCurrentMode()) {

		case SELECTION_MODE:

			// wenn objekt markiert ist, werden markierte elemente verschoben
			// es sei denn, es wird aktuell gerade das markierungsrechteck
			// gezeichnet
			if (this.eArea.isElementSelected(mewz) && !markTangleExists) {
				this.objectDragged = true;
				this.eModel.moveElements(eArea.getNamesOfSelectedElements(),
						mewz.getX() - startX, mewz.getY() - startY);
				this.startX = mewz.getX();
				this.startY = mewz.getY();

				// falls nicht wird das markierungsrechteck gezeichnet
				// es sei denn, es wird gerade ein bauteil verschoben
			} else {
				if (!objectDragged) {

					// exisitiert das markierungsrechteck nicht, wird es neu
					// erstellt
					if (!markTangleExists) {
						markTangleExists = true;
						this.eArea.initializeMarkTangle(mewz.getX(),
								mewz.getY(), mewz.getX(), mewz.getY());

						// existiert es bereits, werden die endkoordinaten neu
						// gesetz
					} else {
						this.eArea.moveMarkTangle(mewz.getX(), mewz.getY());
					}
				}
			}

			// falls momentan ein bauteil verschoben wird, werden neuen
			// koordinaten
			// ans model uebergeben
			if (objectDragged) {
				this.eModel.moveElements(eArea.getNamesOfSelectedElements(),
						mewz.getX() - startX, mewz.getY() - startY);
				this.startX = mewz.getX();
				this.startY = mewz.getY();
			}
			break;

		case MOVE_MODE:

			// veraendern des jviewports des jscrollpanes, auf dem die
			// editorarea liegt
			Container c = eArea.getParent();

			if (c instanceof JViewport) {
				JViewport jv = (JViewport) c;
				Point p = jv.getViewPosition();

				// Korrektur durch moveX und moveY
				int newX = p.x - (arg0.getX() - (int) moveX);
				int newY = p.y - (arg0.getY() - (int) moveY);

				int maxX = eArea.getWidth() - jv.getWidth();
				int maxY = eArea.getHeight() - jv.getHeight();

				if (newX < 0) {
					newX = 0;
				} else if (newX > maxX) {
					newX = maxX;
				}

				if (newY < 0) {
					newY = 0;
				} else if (newY > maxY) {
					newY = maxY;
				}

				jv.setViewPosition(new Point(newX, newY));
			}
			break;

		default:
			break;
		}
	}

	/**
	 * Je nach ausgewaehltem Modus werden bestimmte Operationen durchgefuehrt.
	 */
	@Override
	public void mouseMoved(MouseEvent arg0) {
		mewz.setLocation(arg0.getPoint().getX() / tabs.getZoomFactor(), arg0
				.getPoint().getY() / tabs.getZoomFactor());

		switch (this.toolBar.getCurrentMode()) {

		case SELECTION_MODE:

			// im SELECTION-Modus wird der cursor auf move gesetzt, wenn man
			// ueber
			// einem markiertes bauteil ist
			if (this.eArea.isElementSelected(mewz)) {
				eArea.setCursor(move);

				// ansonsten hat man den normalen mauszeiger
			} else {
				eArea.setCursor(custom);

				// zur anzeige des bildes für die selbstdefinierten bauteile
				LogicViewElement elem = this.eArea.containsElement(mewz);
				if (elem != null) {
					if (UserSettings.paintSDPicture) {
						if (!UserSettings.needControlDownForPicture
								|| arg0.isControlDown()) {
							LogicEditorElement lEE = eModel.getCircuitModel()
									.getUnmodifableElementsOnEditor()
									.get(elem.getElementsName());
							if (showPicPanel == null
									&& lEE instanceof LogicSelfdefinedEditorElement) {

								showPicPanel = new SelfdefinedPicturePanel(lEE,
										mewz, eArea.getiArea());
								showPicPanel.pack();
								showPicPanel.setLocation(arg0
										.getLocationOnScreen());
								showPicPanel.setVisible(true);
								eArea.revalidate();
								eArea.repaint();
							}
						}
					}
				} else {
					if (showPicPanel != null
							&& !showPicPanel.getBounds().contains(
									(int) mewz.getX(), (int) mewz.getY())) {
						showPicPanel.dispose();
						showPicPanel = null;
						eArea.revalidate();
						eArea.repaint();
					}
				}
			}

			break;

		case MOVE_MODE:

			// im MOVE-Modus wird der cursor auf move gesetzt
			eArea.setCursor(move);
			break;

		case INSERTION_MODE:

			// im INSERTION-Modus wird der cursor auf crosshair gesetzt. faehrt
			// man ueber ein bereits existierendes bauteil, wird der cursor
			// veraendert
			if (compPanel.getActualSelected() != null) {
				eArea.setCursor(crosshair);

				if (this.eArea.containsElement(mewz) != null) {

					// wenn der leitungsmodus drin ist (== null) hand anzeigen
					if (compPanel.getActualSelected() == null) {
						eArea.setCursor(hand);

						// sonst den errorcursor
					} else {
						eArea.setCursor(error);
					}
				}
			} else {

				if (containsInputOrOutputDuringMoving(mewz)) {
					this.eArea.setCursor(crosshair);
				} else {
					this.eArea.setCursor(error);
				}

				if (firstConnectionPoint != null) {
					this.eArea.moveNewConnection(mewz);
				}
			}
			break;

		default:
			break;
		}
		// clicked, moved, clicked fuer CONNECTION_MODE:
		// wenn erstes != null, dann Linie mitzeichnen, sonst nicht
	}

	/**
	 * Methode, die ueberprueft, ob ein Input oder ein Output angeklickt wurde
	 * 
	 * @param point
	 *            angeklickter Punkt
	 * @return das input bzw output objekt, falls ja. null falls nein
	 */
	private Object checkIfInputOrOutput(Point2D point) {
		Object object = null;
		double xPos = point.getX();
		double yPos = point.getY();
		double xObject;
		double yObject;
		boolean node = false;

		// es wird geschaut, ob der punkt auf einem bauteil liegt
		LogicViewElement vElement = this.eArea.containsElement(point);
		if (vElement != null
				&& eModel.getCircuitModel().getUnmodifableElementsOnEditor()
						.get(vElement.getElementsName()).getType() == LogicType.NODE) {

			// wurde ein Knoten angeklickt, wird dies vermerkt. Knoten
			// benoetigen im Folgenden eine Sonderrolle
			node = true;
		}

		// liegt der punkt auf einem bauteil und ist der INSERTION-Modus
		// ausgew�hlt, wird ueberprueft, ob man auf einen input oder output
		// geklickt wurde
		if (vElement != null
				&& this.toolBar.getCurrentMode().equals(
						ButtonValues.INSERTION_MODE)) {
			LogicEditorElement eElement = this.eModel.getCircuitModel()
					.getUnmodifableElementsOnEditor()
					.get(vElement.getElementsName());

			// wurde bisher noch kein input oder output angeklickt und auf einen
			// Knoten geklickt, wird dieser Knoten gespeichert und der Input des
			// Knotens zurueckgegeben
			if (firstConnectionPoint == null && node) {
				nodeElement = eElement;
				return eElement.getInputs().get(Input.STD_A);

				// wurde ein Knoten angeklickt und wurde vorher bereits ein
				// knoten angeklickt wird null zurueckgegeben. verbinden zweier
				// Knoten ist verboten
			} else if (node && nodeElement instanceof LogicEditorElement) {
				nodeElement = null;
				return null;
			}

			if (eElement.getType() != LogicType.SWITCH
					&& eElement.getType() != LogicType.CLOCK) {

				// holen der inputs des angeklickten bauteils
				Map<String, Input> inputs = eElement.getInputs();

				// durchgehen und ueberpruefen, ob ein input angeklickt wurde
				for (Map.Entry<String, Input> m : inputs.entrySet()) {
					xObject = m.getValue().getXRelToElement()
							+ vElement.getSourceX()
							+ UtilityConstants.IO_RADIUS / 2;
					yObject = m.getValue().getYRelToElement()
							+ vElement.getSourceY()
							+ UtilityConstants.IO_RADIUS / 2;

					// kreisgleichung zum ueberpruefen
					if ((xPos - xObject) * (xPos - xObject) + (yPos - yObject)
							* (yPos - yObject) <= (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)
							* (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)) {

						// wurde gerade ein knoten angeklickt und wurde zuvor
						// ein
						// output angeklickt und ist der input des knotens noch
						// nicht belegt, wird dieser zurueckgegeben
						if (node && firstConnectionPoint instanceof Output
								&& m.getValue().getSourceOutput() == null) {
							object = m.getValue();
							return object;

							// wurde kein knoten angeklickt und ist der input
							// noch
							// nicht belegt
						} else if (!node
								&& m.getValue().getSourceOutput() == null) {

							// wurde zuvor bereits ein knoten angeklickt, wird
							// dem
							// ersten verbindungspunkt der output des knotens
							// zugewiesen und der input des jetzt angeklickten
							// objekts zurueckgegeben
							if (nodeElement instanceof LogicEditorElement) {
								firstConnectionPoint = nodeElement.getOutputs()
										.get(Output.STD_O);
								nodeElement = null;
								object = m.getValue();
								return object;

								// wurde zuvor kein knoten angeklickt wird der
								// input
								// zurueckgegeben
							} else {
								object = m.getValue();
								return object;
							}
						}
					}
				}
			}
			if (eElement.getType() != LogicType.LAMP) {

				// holen der outputs
				Map<String, Output> outputs = eElement.getOutputs();

				// durchgehen und ueberpruefen, ob ein output angeklickt wurde
				for (Map.Entry<String, Output> m : outputs.entrySet()) {
					xObject = m.getValue().getXRelToElement()
							+ vElement.getSourceX()
							+ UtilityConstants.IO_RADIUS / 2;
					yObject = m.getValue().getYRelToElement()
							+ vElement.getSourceY()
							+ UtilityConstants.IO_RADIUS / 2;

					// kreisgleichung zum ueberpruefen
					if ((xPos - xObject) * (xPos - xObject) + (yPos - yObject)
							* (yPos - yObject) <= (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)
							* (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)) {

						// wurde ein knoten angeklickt und zuvor bereits ein
						// input
						// eines anderen bauteils, wird der output des knotens
						// zurueckgegeben
						if (node && firstConnectionPoint instanceof Input) {
							object = m.getValue();
							return object;

							// wurde kein knoten angeklickt
						} else if (!node) {

							// wurde zuvor ein knoten angeklickt, wird der erste
							// verbindungspunkt auf den eingang des knotens
							// gesetzt
							// und es wird der angeklickte output zurueckgegeben
							if (nodeElement instanceof LogicEditorElement) {
								firstConnectionPoint = nodeElement.getInputs()
										.get(Input.STD_A);
								nodeElement = null;
								object = m.getValue();
								return object;
							}

							// wurde zuvor kein knoten angeklickt wird der
							// angeklickte output zurueckgegeben
							object = m.getValue();
							return object;
						}
					}
				}
			}
		}
		return object;
	}

	/**
	 * Ueberprueft, ob man mit dem Mauszeiger momentan ueber einem Eingang oder
	 * Ausgang ist, bei dem man eine korrekte Linie zeichnen koennte
	 * 
	 * @param point
	 *            aktueller Punkt des Mauszeigers
	 * @return true falls ja - sonst false
	 */
	private boolean containsInputOrOutputDuringMoving(Point2D point) {
		boolean contains = false;

		LogicViewElement vElement = this.eArea.containsElement(point);
		double xPos = point.getX();
		double yPos = point.getY();
		double xObject = 0;
		double yObject = 0;

		// falls man sich in einem Element befindet
		if (vElement != null) {
			LogicEditorElement eElement = this.eModel.getCircuitModel()
					.getUnmodifableElementsOnEditor()
					.get(vElement.getElementsName());

			Map<String, Input> inputs = eElement.getInputs();
			Map<String, Output> outputs = eElement.getOutputs();
			if (eElement.getType() != LogicType.LAMP) {

				// ueberpruefen ob man sich ueber einem ausgang befindet
				for (Map.Entry<String, Output> m : outputs.entrySet()) {
					xObject = m.getValue().getXRelToElement()
							+ vElement.getSourceX()
							+ UtilityConstants.IO_RADIUS / 2;
					yObject = m.getValue().getYRelToElement()
							+ vElement.getSourceY()
							+ UtilityConstants.IO_RADIUS / 2;

					// kreisgleichung zum ueberpruefen
					if ((xPos - xObject) * (xPos - xObject) + (yPos - yObject)
							* (yPos - yObject) <= (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)
							* (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)) {

						// namen des eingangs soll nur bei selbstdefinierten
						// bauteilen angezeigt werden
						if (UserSettings.showInOutTooltip
								&& eElement.getType() == LogicType.SELFDEFINED) {
							tp.setVisible(true);
							tp.setLocation((int) mewz.getX(), (int) mewz.getY());
							tp.setTipText(m.getValue().getOuterName());
							tp.setSize(tp.getPreferredSize());
						}

						// falls zuvor noch kein output angeklickt wurde wird
						// true
						// zurueckgegeben
						if (!(firstConnectionPoint instanceof Output)) {
							contains = true;
							break;
						}
					}
					tp.setVisible(false);
				}
			}

			// hat man noch keinen passenden output gefunden, werden nun die
			// inputs ueberprueft
			if (!contains && eElement.getType() != LogicType.CLOCK
					&& eElement.getType() != LogicType.SWITCH) {
				for (Map.Entry<String, Input> m : inputs.entrySet()) {
					xObject = m.getValue().getXRelToElement()
							+ vElement.getSourceX()
							+ UtilityConstants.IO_RADIUS / 2;
					yObject = m.getValue().getYRelToElement()
							+ vElement.getSourceY()
							+ UtilityConstants.IO_RADIUS / 2;

					// kreisgleichung zum ueberpruefen
					if ((xPos - xObject) * (xPos - xObject) + (yPos - yObject)
							* (yPos - yObject) <= (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)
							* (UtilityConstants.IO_RADIUS / 2 + UserSettings.inputOutputOffset)) {
						if (UserSettings.showInOutTooltip
								&& eElement.getType() == LogicType.SELFDEFINED) {
							tp.setVisible(true);
							tp.setLocation((int) mewz.getX(), (int) mewz.getY());
							tp.setTipText(m.getValue().getOuterName());
							tp.setSize(tp.getPreferredSize());
						}

						// falls zuvor noch kein input angeklickt wurde, wird
						// true zurueckgegeben
						if (!(firstConnectionPoint instanceof Input)) {
							contains = true;
							break;

							// knoten erfordern sonderrolle
						} else if (nodeElement != null) {
							contains = true;
							break;
						}
					}
					tp.setVisible(false);
				}
			}
		} else {
			tp.setVisible(false);
		}

		return contains;
	}

	/**
	 * Methode die ueberprueft ob sich ein Bauteil vom Typ
	 * 
	 * @param type
	 *            Bauteiltyp und der Position
	 * @param xPos
	 *            x-Position
	 * @param yPos
	 *            y-Position mit einem bereits bestehenden Bauteil schneiden
	 *            wuerde oder nicht
	 * @return true falls Schnittpunkt, sonst false
	 */
	private boolean addWillIntersect(LogicType type, double xPos, double yPos) {
		Shape shape = UtilityLogicDesigns.getLogicDesign(type, xPos, yPos);
		// iterierung durch die bauteile und ueberpruefen, ob sich das bauteil
		// mit einem bestehenden bauteil schneidne wuerde
		for (LogicViewElement lVE : eArea.getLogicViewElements()) {
			if (eArea.shapesIntersect(shape, lVE.getDesign())) {
				this.eArea
						.getiArea()
						.addInfoText(
								"Bauteil kann hier nicht eingefügt werden, da "
										+ "es sich mit einem bereits vorhandenen Bauteil "
										+ "schneiden würde");
				return true;
			}
		}
		return false;
	}

	/**
	 * Easter-Egg(geht nur wenn Schaltung leer)
	 */
	public void generateRandomCircuit(int maxElements) {
		if (eModel.getCircuitModel().getUnmodifableElementsOnEditor().isEmpty()) {
			List<Input> inputs = new ArrayList<Input>();
			List<Output> outputs = new ArrayList<Output>();

			// maximal 15 gatter
			int numberOfGates = (int) (Math.random() * maxElements);

			for (int i = 0; i < numberOfGates; i++) {

				// zufaelliger typ wobei ein schalter dabei sein soll
				LogicType randomType;
				if (i == 0) {
					randomType = LogicType.SWITCH;
				} else {
					randomType = LogicType.values()[((int) (Math.random() * (LogicType
							.values().length - 1)))];
				}
				// zufaellige koordinaten
				int randomX;
				int randomY;
				do {
					randomX = (int) (Math.random() * 750);
					randomY = (int) (Math.random() * 600);
				} while (addWillIntersect(randomType, randomX, randomY));

				LogicEditorElement elem = eModel.addLogicElement(randomType,
						null, randomX, randomY);

				if (!LogicType.SETTERS.contains(elem.getType())) {
					inputs.addAll(elem.getInputs().values());
				}

				if (!LogicType.GETTERS.contains(elem.getType())) {
					outputs.addAll(elem.getOutputs().values());
				}

				// zufaellige connections
				for (int j = 0; j < inputs.size(); j++) {
					Input inp = inputs.get(j);
					Output out = outputs.get((int) (Math.random() * outputs
							.size()));
					if (inputs.size() <= outputs.size()) {
						while (out.getTargetInputs().size() != 0) {
							out = outputs.get((int) (Math.random() * outputs
									.size()));
						}
					}

					eModel.setConnection(out, inp);
				}
			}
		}
	}
}
